/* in.c is part of VIP program
 * Written by J. Salvador Arias, 2010
 *      CONICET, INSUE, Instituto Miguel Lillo,
 *      Miguel Lillo 205, S.M. de Tucuman (4000)
 *      Tucuman, Argentina
 *      e-mail: jsalarias@csnat.unt.edu.ar
 *
 * VIP is a free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation and
 * Creative Commons.
 *
 * This file and the VIP program is distributed in the hope
 * that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * To view a copy of this license, visit
 * http://creativecommons.org/licenses/GPL/2.0/
 * or send a letter to
 * Creative Commons, 171 Second Street, Suite 300,
 * San Francisco, California, 94105, USA.
 */

#include "in.h"

/* Create a input stream */
INSTREAM* CreateInStream () {
 INSTREAM* toRet;

 toRet = (INSTREAM*) g_malloc (sizeof (INSTREAM));
 toRet -> stream = NULL;
 toRet -> name = NULL;
 toRet -> line = 0;
 toRet -> len = 0;
 toRet -> state = EOF_REACHED;
 toRet -> nextChar = 0;
 toRet -> actPos = 0;
 return toRet;
}

/* Destroy a input stream */
void DestroyInStream (INSTREAM* in) {
 if (in == NULL) return;
 if (in -> stream != NULL)
   g_free (in -> stream);
 g_free (in -> name);
 g_free (in);
}

/* Set stream to a input file */
SFLAG SetInStreamToFile (INSTREAM* in, gchar* name) {
 ResetInStream (in);
 in -> name = g_strdup_printf (name);
 if (g_file_get_contents (in -> name, &in -> stream, &in -> len, NULL) == FALSE) {
   errorMessage = g_strdup_printf ("\nUnable to open file %s\n", in -> name);
   OnError ();
   return FALSE;
 }
 in -> line = 1;
 in -> state = OPEN_STREAM;
 SkipSpaces (in);
 return TRUE;
}

/* Set stream to a GString */
SFLAG SetInStreamToString (INSTREAM* in, GString* src) {
 ResetInStream (in);
 in -> name = g_strdup_printf ("string");
 in -> stream = g_strdup (src -> str);
 in -> len = src -> len + 1;
 in -> line = 1;
 in -> state = OPEN_STREAM;
 in -> nextChar = in -> stream [0];
 return in -> state;
}

/* Reset stream */
void ResetInStream (INSTREAM* in) {
 if (in == NULL) return;

 if (in -> stream != NULL)
   g_free (in -> stream);
 in -> stream = NULL;
 if (in -> name != NULL)
   g_free (in -> name);
 in -> name = NULL;
 in -> line = 0;
 in -> len = 0;
 in -> state = EOF_REACHED;
 in -> nextChar = 0;
 in -> actPos = 0;
}

/* Read a string, returns the actual file state */
SFLAG ReadString (INSTREAM* in, GString* dest) {
 gchar c;

 if (dest != NULL)
   g_string_erase (dest, 0, -1);
 if (in -> state == EOF_REACHED) return in -> state;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (g_ascii_isspace (c)) break;
   if ((c == ',') || (c == ';') || (c == '=') || (c == ':') || (c == '(') || (c == ')')) break;
   if (dest != NULL) {
     if ((g_ascii_isupper (c)) && (readingMode & ALL_LOWER))
       c += 32;
     g_string_append_c (dest, c);
   }
 }
 /* Put a NULL character in the end */
 if (dest != NULL)
   g_string_append_c (dest, 0);
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
 }
 else
   SkipSpaces (in);
 return in -> state;
}

/* Look for the next string on the file, but keep the actual position */
SFLAG PeekString (INSTREAM* in, GString* dest) {
 SCOUNTER line, pos;
 SFLAG state;
 gchar next;

 if (dest == NULL) return in -> state;
 if (in -> state == EOF_REACHED) return in -> state;
 line = in -> line;
 pos = in -> actPos;
 state = in -> state;
 next = in -> nextChar;
 ReadString (in, dest);
 in -> line = line;
 in -> actPos = pos;
 in -> state = state;
 in -> nextChar = next;
 return state;
}

SFLAG ReadStringUntil (INSTREAM* in, GString* dest, gchar end) {
 gchar c;
 SCOUNTER i;
 SFLAG inside;

 if (dest != NULL)
   g_string_erase (dest, 0, -1);
 if (in -> state == EOF_REACHED) return BAD_IN_FORMAT;
 c = 0;
 inside = FALSE;
 for (i = 0; in -> actPos < in -> len; ++ in -> actPos, ++ i) {
   c = in -> stream [in -> actPos];
   if (c == 34)
     inside = !inside;
   if ((c == end) && (inside == FALSE)) break;
   if (c == '\n') break;
   if ((dest != NULL) && (c != '\r')) {
     if ((g_ascii_isupper (c)) && (readingMode & ALL_LOWER))
       c += 32;
     else if (g_ascii_isspace (c))
       c = 32;
     else if ((c == '<') || (c == '>'))
       c = '_';
     if (c == '&')
       g_string_append (dest, "and");
     else
       g_string_append_c (dest, c);
   }
 }
 if ((dest != NULL) && (dest -> len > 0))
   g_string_append_c (dest, 0);
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
   return in -> state;
 }
 else {
   ++ in -> actPos;
   in -> nextChar = in -> stream [in -> actPos];
 }
 if (c != end) {
   ++ in -> line;
   return END_OF_IN_LINE;
 }
 return END_CHAR_FOUND;
}

/* Read a bounded comment, returns the actual file state,
   if no string is provided it ignores the inside part of the comment */
SFLAG ReadBoundComment (INSTREAM* in, GString* dest, gchar start, gchar end) {
 gchar c;

 if (dest != NULL)
   g_string_erase (dest, 0, -1);
 if (in -> state == EOF_REACHED) return BAD_IN_FORMAT;
 if (in -> nextChar != start) return BAD_IN_FORMAT;
 ++ in -> actPos;
 SkipSpaces (in);
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == end) break;
   if (dest != NULL)
     g_string_append_c (dest, c);
 }
 /* Put a NULL character in the end */
 if ((dest != NULL) && (dest -> len > 0))
   g_string_append_c (dest, 0);
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
   return in -> state;
 }
 else {
   ++ in -> actPos;
   SkipSpaces (in);
 }
 return 1;
}

/* Read an integer value */
SFLAG ReadInt (INSTREAM* in, SINTEGER* number) {
 gchar c;
 SINTEGER acum;
 SINTEGER kind;
 SCOUNTER prevPos;

 if (in -> state == EOF_REACHED) return in -> state;
 acum = 0;
 c = in -> nextChar;
 if (c == '-') {
   ++ in -> actPos;
   kind =-1;
 }
 else
   kind = 1;
 prevPos = in -> actPos;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (!g_ascii_isdigit (c)) break;
   acum *= 10;
   acum += c - '0';
 }
 if (prevPos == in -> actPos) return BAD_IN_FORMAT;
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
 }
 else
   SkipSpaces (in);
 if (number != NULL)
   *number = (kind * acum);
 return TRUE;
}

/* Read a float pointed value */
SFLAG ReadReal (INSTREAM* in, SREAL* number) {
 SREAL acum, dig;
 gchar c;
 SINTEGER num;
 SFLAG state, withExpo;
 SCOUNTER prevPos;
 SCOUNTER i, expo;

 num = 0;
 state = ReadInt (in, &num);        /* Reads the integer part */
 c = in -> nextChar;
 acum = 0.000000;
 dig = 1;
 prevPos = in -> actPos;
 withExpo = FALSE;
 if (c == '.') {            /* Reads the decimal part */
   ++ in -> actPos;
   for (; in -> actPos < in -> len; ++ in -> actPos) {
     c = in -> stream [in -> actPos];
     if (!g_ascii_isdigit (c)) break;
     acum *= 10;
     acum += (SREAL) (c - '0');
     dig *= 10;
   }
   if (in -> actPos == in -> len) {
     in -> nextChar = 0;
     in -> state = EOF_REACHED;
   }
   else
     SkipSpaces (in);
   acum /= dig;
 }
 if (c == 'e') {
   ReadChar (in, NULL);
   state = ReadInt (in, &expo);
   dig = 1;
   for (i = 1; i <= expo; ++ i)
     dig *= 10;
   withExpo = TRUE;
 }
 if ((state == BAD_IN_FORMAT) && (prevPos == in -> actPos)) return BAD_IN_FORMAT;
 if (num >= 0)
   acum += num;
 else
   acum = num - acum;
 if (number != NULL)
   *number = acum;
 if (withExpo == TRUE)
   * number /= dig;
 return in -> state;
}

/* Returns the next character and moves the stream to the next readable char */
SFLAG ReadChar (INSTREAM* in, gchar* dest) {
 if (dest != NULL)
   *dest = in -> nextChar;
 if (in -> state != EOF_REACHED) {
   ++ in -> actPos;
   SkipSpaces (in);
 }
 return in -> state;
}

/* Ignore space characters of the data set, and count the EOL found */
void SkipSpaces (INSTREAM* in) {
 gchar c;

 c = 0;
 if (in -> state == EOF_REACHED) return;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (!g_ascii_isspace (c)) break;
   if (c == '\n')
     ++ in -> line;
 }
 if ((in -> actPos == in -> len) || (c == '\0')) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
   return;
 }
 in -> nextChar = c;
}
